Exercise (Week 8)
Table of Contents
DUE: Thu 28 July 2022 09:10:00
1 Monadic Input/Output
Download the exercise tarball and extract it to a directory in your
home directory at CSE. This tarball contains a file, called Ex06.hs
,
wherein you will do all of your programming.
To test your code, run the following shell commands to open a GHCi session:
$ 3141 newclass starting new subshell for class COMP3141... $ cabal repl Resolving dependencies... Configuring Ex06-1.0... Preprocessing executable 'Ex06' for Ex06-1.0.. GHCi, version 8.2.2: http://www.haskell.org/ghc/ :? for help [1 of 1] Compiling Ex06 (Ex06.hs, interpreted) Ok, one module loaded. *Ex06> onlyUnique "input.txt" "output.txt" ...
Calling IO
actions in GHCi
as above will execute them, including
their side effects.
Note that you will submit only Ex06.hs
, so only make changes
to that file.
Download the exercise tarball and extract it to a directory on
your local machine. This tarball contains a file, called Ex06.hs
,
wherein you will do all of your programming.
To test your code, run the following shell commands to open a GHCi session:
$ stack repl Configuring GHCi with the following packages: Ex06 Using main module: 1. Package 'Ex06' component exe:Ex06 ... GHCi, version 8.2.2: http://www.haskell.org/ghc/ :? for help [1 of 2] Compiling Ex06 (Ex06.hs, interpreted) [2 of 2] Compiling Main (Main.hs, interpreted) Ok, two modules loaded. *Main Ex06> onlyUnique "input.txt" "output.txt" ...
Calling IO
actions in GHCi
as above will execute them, including
their side effects. You can run your main function using stack run
.
Note that you will only need to submit Ex06.hs
, so only make changes
to that file.
1.1 Basic I/O (4 Marks)
Your first task will be to implement the function
onlyUnique :: FilePath -> FilePath -> IO ()
which should define a procedure that: reads the given input file (the first argument),
throws away all lines in the file that appear more than once, then writes the remaining
(unique) lines to the given output file.
The input file should only be read, not changed.
Note that the FilePath
type is just a type synonym for String
.
To read files, you can just use the function readFile
in this simple exercise; you can find a variety of other file manipulation functions in System.IO
, but you won't need them for this problem.
1.2 State and Testing IO (5 Marks)
There is a well-known number guessing game where the player attempts to guess a secret integer number that the computer has chosen randomly between 1 and 100.
If the player guesses incorrectly, the only information they receive is whether the secret number is lower or higher than the player's guess.
The player wins if they successfully guess the secret. But if the player fails to guess the secret number in a certain number of turns, they lose.
You will be provided with an implementation of this game in Haskell. You should study the implementation, and the notes below, to make sure that you understand how it works. Your task will be to develop a simple AI which can play this game.
Notes
We define a bunch of data classes to store the game state, the possible outcomes of the game,
and the possible feedback responses the player may get (Lower
, Higher
). This is fairly
straightforward.
The first surprise comes when we define the Player
data type. Since the game has to support both
human and AI player, we will define Player
as a type that consists of two monadic callback functions.
One of these functions, guess
is the one that's called when the player has to make a guess.
The other, signalWrong
is the function that is used to tell the player whether their last guess
was too high (Lower
, meaning go lower), or too low (Higher
, meaning go higher).
data Player m = Player { guess :: m Int , signalWrong :: Response -> m () }
We have the m
as a type parameter, because different players will need different monads to handle
responses. A human player will need the IO
monad: to let the player make a guess, we will need a
guess function with the type guess :: IO Int
(i.e. a procedure that asks the player for a guess,
then returns the answer).
The AI will not need to do IO: it can make its `guess` by consulting a data structure that contains its state, and incorporate the `Response` by modifying this data structure. Therefore, an AI player can play using the `State AiState` monad.
The guessing game's main loop, gameLoop
, is defined generically so that it works with any monad m
,
as long as we can provide a Player
that acts in that monad. You can play
the guessing game yourself in the IO
monad with the human
player by calling play
:
Your task is to define a new player, ai
, which plays the game automatically, by acting in the
State AiState
monad instead of the IO
monad:
data AiState = AiState { lowerBound :: Int, upperBound :: Int } deriving (Show, Eq) ai :: Player (State AiState) ai = Player { guess = aiGuess, signalWrong = aiWrong } where aiGuess = ... aiWrong = ...
We would like to test that the AI implementation satisfies some properties. Firstly, we would like the
ai
player never to repeat a guess, that is, the ai
player should guess the number in at most 100
guesses. This is implemented in prop_winEventually
, which you can run using quickCheck
as usual.
A more advanced AI should play optimally: it is known that the game can always be won using 7 guesses.
This is implemented in the second test, prop_winsOptimally
.
2 Envoi
This is the last exercise of COMP3141. By the time you're done with it, you will be ready to move on to more advanced materials: read tutorials about certain libraries such as the web programming library Yesod; follow along with articles explaining more advanced language features online; or try to implement some useful project on your own.
If you're looking to practice simple IO further, you can always create user interfaces for our previous projects. E.g. you could try to build a REPL for our stack-based calculator from the previous exercise. Here are some more advanced tutorials you might later want to try (some are more difficult than others):
- Learn blaze-html, a simple and fast monad for writing HTML templates.
- Try to develop a Discord bot using discord-haskell.
- Write a library for searching the file system.
- Learn parser combinators by writing a JSON parser from scratch in 200 lines.
- Find out how to combine different monads using monad transformers.
- Or if you don't like writing things from scratch, check out a convenient JSON library, Aeson.
- Write your first Haskell web app.
- Learn about one of the most powerful abstractions in Haskell, Lenses.
3 Submission instructions
You can submit your exercise by typing:
$ give cs3141 ex06 Ex06.hs
on a CSE terminal. Your file must be named Ex06.hs
(case-sensitive!).
A dry-run test will partially autotest your solution at submission time. To get full marks, you will need to perform further testing yourself.